/* 
 * File:   Game.cpp
 * Author: mvdr
 * 
 * Created on March 19, 2013, 3:01 PM
 */

#include "Game.h"
#include <algorithm>
#include <iostream>
#include <utility>
#include <queue>
#include "Utilities.h"
#include <map>
#include <climits>
#include <limits>

using namespace std;

Game::Game(bool magic, bool infinite, bool kooi, bool full, bool verbose, bool count):
d_questionStack(0),
d_kooiStack(0),
d_stateStack(0),
d_currState(0),
d_stateCounter(0),
d_currPlayer(0),
d_magic(magic),
d_verbose(verbose),
d_cutOffStates(3),
d_infinite(infinite),
d_fullCheck(full),
d_kooi(kooi),
d_skipRounds(0),
d_infGain(20, -1),
d_infGainFull(20, -1),
d_countInfGain(count)
{    
    for (size_t i = 0; i != d_questions.size(); ++i)    
        d_questionStack.push_back(i);

    
    for (size_t i = 0; i != d_model.numberOfStates(); ++i)
        d_stateStack.push_back(i);
    
    random_shuffle(d_stateStack.begin(), d_stateStack.end());
}

vector<size_t> Game::run() {
    shuffleQuestions();
    
    d_kooiStack.clear();
    d_kooiStack = d_questionStack;
    
    
    d_model.initRelations();    
    d_currState = d_stateStack[d_stateCounter++];
    
    
    for (int p = 0; p != 3; ++p)
        d_cutOffStates[p].clear();
        
    return startPlaying();
}

void Game::shuffleQuestions() {
    random_shuffle(d_questionStack.begin(), d_questionStack.end());
}

vector<size_t> Game::startPlaying() {
    // loop through turns until there is a winner    
    for (size_t turn = 0; turn != 3 * d_questionStack.size(); ++turn)    
    {
        if (d_kooiStack.size() == 0)
            d_kooiStack = d_questionStack;
        
        if (d_verbose)
        {
            cout << "\033[2J\033[1;1H" 
                << "=======================================================\n"
                << "GAME " << prettyString(d_stateCounter, 4) << " // "
                << "TURN " << prettyString(turn + 1, 2) << " // TRUE STATE: "
                << d_model.stateAt(d_currState) << '\n'
                << "=======================================================\n\n";
            printInfo();
        }        
        
        d_currPlayer = turn % 3;
        
        int currQuestion = d_questionStack[turn % d_questionStack.size()];        
        
        if (d_kooi){
            currQuestion = minInfQuestion();
            removeQuestion(currQuestion);
        }
        
        if (d_countInfGain)
        {            

            d_infGain[currQuestion] =  d_infGain[currQuestion] == -1 ?
                informationGain(currQuestion, false) : 
                d_infGain[currQuestion] + informationGain(currQuestion, false);
            
            d_infGainFull[currQuestion] =  d_infGainFull[currQuestion] == -1 ?
                informationGain(currQuestion, true) : 
                d_infGainFull[currQuestion] + informationGain(currQuestion, true);
        }
             
        
        State trueState = d_model.stateAt(d_currState);
        
        bool answer = d_questions.answer(d_currPlayer, currQuestion, trueState); 
        
        if (d_verbose)
            d_questions.printAnswer(d_currPlayer, currQuestion, answer);
            
        
        updateModel(answer, currQuestion);
        
        if(anybodyKnows())
        {
            endGamePrint(turn);            
            return returnVector(turn);
        }        
        else if (turn < 3 * d_questionStack.size() - 1 && d_magic)
        {
            if (d_verbose)
            {
                cout << "\r(continue)  \b\b";
                if (d_stateCounter >= d_skipRounds)
                    pauseTilEnter();         
            }
            
            bool modelChanged = true;
            int epistRuns = 0;            
            
            while(modelChanged & !anybodyKnows())
            {
                epistRuns++;
                modelChanged = epistemagic();
                if (!d_infinite)
                    break;
            }
            
            if (d_verbose)
                cout << "Ran epistemagic " << epistRuns << " times.\n";
            
            if(anybodyKnows())
            {
                endGamePrint(turn);            
                return returnVector(turn);
            }                       

        }
        
        bool considerTrueStatePossible = 
        d_model.playerConsidersStatePossible(0, d_currState, d_currState) &
        d_model.playerConsidersStatePossible(1, d_currState, d_currState) &
        d_model.playerConsidersStatePossible(2, d_currState, d_currState);
        
        if (!considerTrueStatePossible)
            cout << "\nERROR!!! True state not considered possible by at " <<
                    "least one player!\n";
        
        if (d_verbose)
        {
            cout << "(continue)  \b\b";
            if (d_stateCounter >= d_skipRounds)
                pauseTilEnter();         
        }
    }
    cout << "No winners :-(\n";
    return vector<size_t> ({0, 0});
    
}

void Game::updateModel(bool answer, int currQuestion) {
    // remove all states inconsistent with the current answers
    // from the model
    
    vector<vector<int> > printStates;
    for (int p = 0; p != 3; ++p)    
        printStates.push_back(vector<int> (0));
    
    int dots = 0;    
    
    for (size_t p = 0; p != 3; ++p)
    {
        for (size_t idx = 0; idx != d_model.numberOfStates(); ++idx) {         
            if (d_magic & d_verbose & (idx % 5 == 0)) {
                cout << "\rworking";
                switch(++dots % 4) {
                    case 1:
                        cout << ".  ";
                        break;
                    case 2:
                        cout << ".. ";
                        break;
                    case 3:
                        cout << "...";
                        break;
                    default:
                        cout << "   ";
                }
            }
            if(d_magic || d_model.playerConsidersStatePossible(p, d_currState, idx))
            {
                State tempState = d_model.stateAt(idx);
                bool tempAnswer = d_questions.answer(d_currPlayer, currQuestion, tempState);
                if (tempAnswer != answer)
                {
                    if (d_model.playerConsidersStatePossible(p, d_currState, idx))
                            printStates[p].push_back(idx);
                    d_model.cutOff(idx, p);
                    d_cutOffStates[p].insert(idx);
                }
            }
        }
    }
    
    cout << '\r';
    
    if (d_verbose)
        for (size_t p = 0; p != 3; ++p)
        {
            if ( p != d_currPlayer)
            {
                if (printStates[p].empty())
                    cout << "Nothing has changed for player " << p + 1 << ".\n\n";
                else
                {
                    cout << "Player " << p + 1 << " no longer considers the"
                            << " following states possible:";
                    size_t counter = 0;
                    for (size_t i = 0; i != printStates[p].size(); ++i)
                    {        
                        if (counter++ % 3 == 0)
                                cout << '\n';
                        cout << d_model.stateAt(printStates[p][i]) << "; ";                    
                    }
                    cout << "\b\b.\n\n";


                }
            }
        }
        
    
}

bool Game::anybodyKnows() const {    
    for (size_t p = 0; p != 3; ++p)
    {
        if (d_model.playerKnowsState(p, d_currState))
            return true;
    }
    return false;
}

size_t Game::numberOfWinners() const {
    size_t winners = 0;
    for (size_t p = 0; p != 3; ++p)
    {
        if (d_model.playerKnowsState(p, d_currState))
            ++winners;
    }
    
    return winners;
}

bool Game::epistemagic() {
    // given that nobody knows their cards yet, remove all states
    // in which a player would have known her cards from the model
    vector<int> removeCandidates(0);
    map<int, vector<int> > printStates;
    bool retVal = false;
    
    for (int p = 0; p != 3; ++p)        
        for (size_t s = 0; s != d_model.numberOfStates(); ++s)        
            if (d_model.playerKnowsState(p, s))                         
            {
                removeCandidates.push_back(s);                
                if (printStates.count(s) == 0)
                    printStates[s] = vector<int>(1, p);
                else
                    printStates[s].push_back(p);
            }
    
    vector<map<int, vector<int> > > playerPrintStates;
    for (size_t p = 0; p != 3; ++p)
    {
        map<int, vector<int> > tmpMap;
        playerPrintStates.push_back(tmpMap);
        for (auto itr = printStates.begin(); itr != printStates.end(); ++itr)
            if (d_model.playerConsidersStatePossible(p, d_currState, itr->first))
                playerPrintStates[p].insert(*itr);
    }
     
    for (size_t i = 0; i != removeCandidates.size(); ++i)
        for (size_t p = 0; p != 3; ++p)
        {
            if (d_cutOffStates[p].count(removeCandidates[i]) == 0)
                retVal = true;
            d_model.cutOff(removeCandidates[i], p);  
            d_cutOffStates[p].insert(removeCandidates[i]);            
        }

            
    
    if (d_verbose)
    {
        cout << "\nAnnouncement: No player knows which cards are on her head!\n\n";
        for (int p = 0; p != 3; ++p)
        {
            if (playerPrintStates[p].empty())
                cout << "Nothing has changed for player " << p + 1 << ".\n\n";
            else
            {
                cout << "Player " << p + 1 << " no longer considers the following"
                        << " states possible:\n";
                for (auto itr = playerPrintStates[p].begin(); 
                        itr != playerPrintStates[p].end(); ++itr)
                {
                    cout << "- " << d_model.stateAt(itr->first) << "\n  ";
                    because(itr->second);                    
                }
                cout << '\n';
            }
        }
    }
    
    return retVal;
    
}

void Game::printInfo() const {
    for (int p = 0; p != 3; ++p)
        d_model.printPossibleStates(p, d_currState);
}

void Game::because(vector<int> const &player)
{
    if (player.size() == 1)
        cout << "(because player " << player[0] + 1 << " would have known her"
             << " cards by now if that\n   was the true state.)\n";
    else
        cout << "(because players " << player[0] + 1 << " and " << player[1] + 1
             << " would have known their cards by now if\n   that was the true"
             << " state.)\n";
}

int Game::minInfQuestion()
{
    int minInfGain = INT_MAX;
    int minQuestion = 0;
    for (int q = 0; q != d_kooiStack.size(); ++q)
    {
        int infGain = informationGain(d_kooiStack[q]);
        if (infGain < minInfGain)
        {
            minInfGain = infGain;
            minQuestion = d_kooiStack[q];
        }
    }
    
    if (d_verbose)
        cout << "Player " << d_currPlayer + 1 << " chose question " <<
                minQuestion + 1 << " because it has the minimal information gain: " << minInfGain
                << ".\n";
        
    return minQuestion;
}

int Game::informationGain(int q, bool full)
{
    int infGain = 0;
    State trueState = d_model.stateAt(d_currState);    
    bool answer = d_questions.answer(d_currPlayer, q, trueState); 
    
    for (int p = 0; p != 3; ++p)
    {
        for (size_t idx = 0; idx != d_model.numberOfStates(); ++idx) {                     
            if(((d_fullCheck || full) & (d_cutOffStates[p].count(idx) == 0))
                    || d_model.playerConsidersStatePossible(p, d_currState, idx))
            {
                State tempState = d_model.stateAt(idx);
                bool tempAnswer = d_questions.answer(d_currPlayer, q, tempState);
                if (tempAnswer != answer)
                    ++infGain;                
            }
        }
    }
    
    return infGain;
}

void Game::removeQuestion(int q)
{
    vector<int>::iterator removeCandidate;
    for (auto itr = d_kooiStack.begin(); itr != d_kooiStack.end(); ++itr)
        if (*itr == q)
            removeCandidate = itr;
    
    d_kooiStack.erase(removeCandidate);
}

void Game::endGamePrint(int turn)
{
    if (d_countInfGain)
    {
        writeResult(d_infGain, true, "gain-per-question-true.csv");
        writeResult(d_infGainFull, true, "gain-per-question-full.csv");
        for (int i = 0; i != d_questionStack.size(); ++i)
        {
            d_infGain[i] = numeric_limits<size_t>::max();
            d_infGainFull[i] = numeric_limits<size_t>::max();
        }
    }
        
    
    if (d_verbose)
    {
        cout << '\r' << numberOfWinners() << " winner";
        if (numberOfWinners() > 1)
            cout << 's';
        cout << " after " << turn + 1 << " turns!!!\n";

        cout << "(continue)";
        if (d_stateCounter >= d_skipRounds)
            pauseTilEnter();
    }
}

vector<size_t> Game::returnVector(size_t turn) const
{
    vector<size_t> returnVec(1, numberOfWinners());
    
    for (int p = 0; p != 3; ++p)
    {
        if (d_model.playerKnowsState(p, d_currState))
            returnVec.push_back(1);
        else
            returnVec.push_back(0);
    }
    
    returnVec.push_back(turn + 1);
    return returnVec;
}
